﻿/*
Copyright (c) 2009 Vratislav Cermak (vratacermak@gmail.com)
 
This file is part of TeachMe Project. See http://code.google.com/p/teach-me for more information.
 
TeachMe is an open source project. Using of TeachMe is free of charge.
It is distributed under GNU General Public License version 3 (GPLv3).
GPLv3 can be found at http://www.gnu.org/licenses/gpl.html.
 
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using TeachMe.Application.Win32;

namespace TeachMe.Application.Forms
{
    public partial class EntriesForm : Form
    {
        public event EventHandler<EventArgs> EscapeDown;

        private readonly ColorMatrix _matrix = new ColorMatrix();
        private readonly ImageAttributes _imageAttributes = new ImageAttributes();
        private bool _topMost;
        private Bitmap _buffer;

        public EntriesForm()
        {
            InitializeComponent();
        }

        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                cp.ExStyle |= (Constants.WS_EX_TOOLWINDOW | Constants.WS_EX_LAYERED);
                return cp;
            }
        }

        public new Point Location
        {
            get
            {
                return base.Location;
            }
            set
            {
                NativeMethods.SetWindowPos(Handle, _topMost ? Constants.HWND_TOPMOST : Constants.HWND_NOTOPMOST, value.X, value.Y, 0, 0, Constants.SWP_NOSIZE | Constants.SWP_NOACTIVATE);
            }
        }

        public new Size Size
        {
            get
            {
                return base.Size;
            }
            set
            {
                NativeMethods.SetWindowPos(Handle, _topMost ? Constants.HWND_TOPMOST : Constants.HWND_NOTOPMOST, 0, 0, value.Width, value.Height, Constants.SWP_NOMOVE | Constants.SWP_NOACTIVATE);
            }
        }

        public new bool TopMost
        {
            get
            {
                return _topMost;
            }
            set
            {
                _topMost = value;

                NativeMethods.SetWindowPos(Handle, _topMost ? Constants.HWND_TOPMOST : Constants.HWND_NOTOPMOST, 0, 0, 0, 0, Constants.SWP_NOSIZE | Constants.SWP_NOACTIVATE | Constants.SWP_NOMOVE);
            }
        }

        public void Draw()
        {
            if (_buffer == null)
            {
                return;
            }

            if (_buffer.PixelFormat != PixelFormat.Format32bppArgb)
            {
                throw new NotSupportedException();
            }

            IntPtr screenDc = NativeMethods.GetDC(IntPtr.Zero);
            IntPtr memDc = NativeMethods.CreateCompatibleDC(screenDc);
            IntPtr hBitmap = IntPtr.Zero;
            IntPtr oldBitmap = IntPtr.Zero;

            try
            {
                // TODO: _buffer.GetHbitmap takes lot of time so try to work directly with it
                hBitmap = _buffer.GetHbitmap(Color.FromArgb(0));
                oldBitmap = NativeMethods.SelectObject(memDc, hBitmap);

                SIZE size = new SIZE(_buffer.Width, _buffer.Height);
                POINT pointSource = new POINT(0, 0);
                POINT topPos = new POINT(Left, Top);
                BLENDFUNCTION blend = new BLENDFUNCTION();
                blend.BlendOp = Constants.AC_SRC_OVER;
                blend.BlendFlags = 0;
                blend.SourceConstantAlpha = 255;
                blend.AlphaFormat = Constants.AC_SRC_ALPHA;

                NativeMethods.UpdateLayeredWindow(Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, Constants.ULW_ALPHA);
            }
            finally
            {
                NativeMethods.ReleaseDC(IntPtr.Zero, screenDc);
                if (hBitmap != IntPtr.Zero)
                {
                    NativeMethods.SelectObject(memDc, oldBitmap);
                    NativeMethods.DeleteObject(hBitmap);
                }
                NativeMethods.DeleteDC(memDc);
            }
        }

        public void ClearBitmapBuffer()
        {
            if (_buffer != null)
            {
                _buffer.Dispose();
            }

            // i cannot create bitmap 0x0 under wine so i will just consume error and test if
            // instance is not null before using it

            try
            {
                Logging.Debug(GetType(), "Creating bitmap, width = '{0}' and height = '{1}'.", Width, Height);

                _buffer = new Bitmap(Width, Height);
            }
            catch(Exception e)
            {
                Logging.Error(GetType(), e, "Bitmap could not be created.");

                _buffer = null;
            }
        }

        public void UpdateBitmapBuffer(Bitmap bitmap, Point location, int opacity)
        {
            if (_buffer == null)
            {
                return;
            }

            ClearBufferPortion(new Rectangle(location, bitmap.Size));

            using (Graphics graphics = Graphics.FromImage(_buffer))
            {
                if (opacity >= 255)
                {
                    graphics.DrawImage(bitmap, location);
                }
                else
                {
                    _matrix.Matrix33 = (opacity / 255f);
                    _imageAttributes.SetColorMatrix(_matrix, ColorMatrixFlag.Default, ColorAdjustType.Default);

                    graphics.DrawImage(bitmap, new Rectangle(location, bitmap.Size), 0, 0, bitmap.Width, bitmap.Height, GraphicsUnit.Pixel, _imageAttributes);
                }
            }
        }

        private void ClearBufferPortion(Rectangle rectangle)
        {
            if (_buffer == null)
            {
                return;
            }

            BitmapData data = _buffer.LockBits(rectangle, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
            
            byte[] empty = new byte[data.Width * 4];

            for (int y = 0; y < data.Height; y++)
            {
                Marshal.Copy(empty, 0, new IntPtr(data.Scan0.ToInt32() + y * data.Stride), empty.Length);
            }

            _buffer.UnlockBits(data);
        }

        private void OnKeyDown(object sender, KeyEventArgs e)
        {
            if ((e.KeyCode & Keys.Escape) == Keys.Escape)
            {
                EventHandler<EventArgs> handler = EscapeDown;
                if (handler != null)
                {
                    handler(this, EventArgs.Empty);
                }
            }
        }
    }
}
